/*

Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     licenses@blazegraph.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/
package com.bigdata.rdf.internal.constraints;

import java.util.Map;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;
import org.openrdf.model.Literal;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.query.algebra.evaluation.ValueExprEvaluationException;
import org.openrdf.query.algebra.evaluation.util.QueryEvaluationUtil;

import com.bigdata.bop.BOp;
import com.bigdata.bop.IBindingSet;
import com.bigdata.bop.IConstant;
import com.bigdata.bop.IValueExpression;
import com.bigdata.bop.NV;
import com.bigdata.rdf.error.SparqlTypeErrorException;
import com.bigdata.rdf.internal.IV;
import com.bigdata.rdf.model.BigdataLiteral;
import com.bigdata.rdf.model.BigdataValueFactory;
import com.bigdata.rdf.sparql.ast.GlobalAnnotations;

/**
 * @see http://www.w3.org/2009/sparql/docs/query-1.1/rq25.xml#func-replace
 */
public class ReplaceBOp extends IVValueExpression<IV> implements INeedsMaterialization {

	private static final transient Logger log = Logger.getLogger(ReplaceBOp.class);

    public interface Annotations extends XSDBooleanIVValueExpression.Annotations {
    	
        /**
         * The cached regex pattern.
         */
        public String PATTERN = ReplaceBOp.class.getName()
                + ".pattern";
        
    }

    private static Map<String,Object> anns(
			final IValueExpression<? extends IV> pattern,
			final IValueExpression<? extends IV> flags,
			final GlobalAnnotations globals) {
    	
    	if (pattern instanceof IConstant && 
    			(flags == null || flags instanceof IConstant)) {
    		
    		final IV parg = ((IConstant<IV>) pattern).get();
    		
    		final IV farg = flags != null ?
    				((IConstant<IV>) flags).get() : null;
    				
			if (parg.hasValue() && (farg == null || farg.hasValue())) {
    		
				final Value pargVal = parg.getValue();
				
				final Value fargVal = farg != null ? farg.getValue() : null;
				
	    		return anns(globals,
	    				new NV(Annotations.PATTERN, 
	    						getPattern(pargVal, fargVal)));
	    		
			}
    		
    	}
    		
		return anns(globals);
    	
    }
    
	/**
	 * Construct a replace bop without flags.
	 */
    @SuppressWarnings("rawtypes")
	public ReplaceBOp(
			final IValueExpression<? extends IV> var, 
			final IValueExpression<? extends IV> pattern,
			final IValueExpression<? extends IV> replacement,
			final GlobalAnnotations globals) {
        
        this(new BOp[] { var, pattern, replacement }, anns(pattern, null, globals));

    }
    
	/**
	 * Construct a replace bop with flags.
	 */
    @SuppressWarnings("rawtypes")
	public ReplaceBOp(
			final IValueExpression<? extends IV> var, 
			final IValueExpression<? extends IV> pattern,
			final IValueExpression<? extends IV> replacement,
			final IValueExpression<? extends IV> flags,
			final GlobalAnnotations globals) {
        
        this(new BOp[] { var, pattern, replacement, flags }, anns(pattern, flags, globals));

    }
    
    /**
     * Required shallow copy constructor.
     */
    public ReplaceBOp(final BOp[] args, final Map<String, Object> anns) {

    	super(args, anns);
    	
        if (args.length < 2 || args[0] == null || args[1] == null)
            throw new IllegalArgumentException();

    }

    /**
     * Constructor required for {@link com.bigdata.bop.BOpUtility#deepCopy(FilterNode)}.
     */
    public ReplaceBOp(final ReplaceBOp op) {
        super(op);
    }
    
	@Override
	public Requirement getRequirement() {
		return Requirement.SOMETIMES;
	}

	@Override
    @SuppressWarnings("rawtypes")
    public IV get(final IBindingSet bs) {
        
        @SuppressWarnings("rawtypes")
        final Literal var = getAndCheckLiteralValue(0, bs);
        
        @SuppressWarnings("rawtypes")
        final Literal pattern = getAndCheckLiteralValue(1, bs);

        @SuppressWarnings("rawtypes")
        final Literal replacement = getAndCheckLiteralValue(2, bs);
        
        @SuppressWarnings("rawtypes")
        final Literal flags = arity() > 3 ? getAndCheckLiteralValue(3, bs) : null;
        
        if (log.isDebugEnabled()) {
        	log.debug("var: " + var);
        	log.debug("pattern: " + pattern);
        	log.debug("replacement: " + replacement);
        	log.debug("flags: " + flags);
        }
        
        try {

        	final BigdataLiteral l = 
        		evaluate(getValueFactory(), var, pattern, replacement, flags);
        	
        	return super.asIV(l, bs);
        	
        } catch (ValueExprEvaluationException ex) {
        	
        	throw new SparqlTypeErrorException();
        	
        }

    }
    
    /**
     * Lifted directly from Sesame's Replace operator.
     * 
     * FIXME The Pattern should be cached if the pattern argument and flags are
     * constants.
     * 
     * @see <a href="http://sourceforge.net/apps/trac/bigdata/ticket/516">
     *      REGEXBOp should cache the Pattern when it is a constant </a>
     */
	public BigdataLiteral evaluate(final BigdataValueFactory valueFactory, final Value... args)
			throws ValueExprEvaluationException {
		if (args.length < 3 || args.length > 4) {
			throw new ValueExprEvaluationException(
					"Incorrect number of arguments for REPLACE: " + args.length);
		}

		try {
			Literal arg = (Literal) args[0];
			Literal pattern = (Literal) args[1];
			Literal replacement = (Literal) args[2];
			Literal flags = null;
			if (args.length == 4) {
				flags = (Literal) args[3];
			}

			if (!QueryEvaluationUtil.isStringLiteral(arg)) {
				throw new ValueExprEvaluationException(
						"incompatible operand for REPLACE: " + arg);
			}

			if (!QueryEvaluationUtil.isSimpleLiteral(replacement)) {
				throw new ValueExprEvaluationException(
						"incompatible operand for REPLACE: " + replacement);
			}

			String argString = arg.getLabel();
			String replacementString = replacement.getLabel();

			Pattern p = (Pattern) getProperty(Annotations.PATTERN);
			if (p == null) {
				p = getPattern(pattern, flags);
			}
			
			String result = p.matcher(argString).replaceAll(replacementString);

			String lang = arg.getLanguage();
			URI dt = arg.getDatatype();

			if (lang != null) {
				return valueFactory.createLiteral(result, lang);
			} else if (dt != null) {
				return valueFactory.createLiteral(result, dt);
			} else {
				return valueFactory.createLiteral(result);
			}
		} catch (ClassCastException e) {
			throw new ValueExprEvaluationException("literal operands expected",
					e);
		}

	}    
    
    private static Pattern getPattern(final Value pattern, final Value flags) 
			throws IllegalArgumentException {
		
		if (!QueryEvaluationUtil.isSimpleLiteral(pattern)) {
			throw new IllegalArgumentException(
					"incompatible operand for REPLACE: " + pattern);
		}

		String flagString = null;
		if (flags != null) {
			if (!QueryEvaluationUtil.isSimpleLiteral(flags)) {
				throw new IllegalArgumentException(
						"incompatible operand for REPLACE: " + flags);
			}
			flagString = ((Literal) flags).getLabel();
		}

		String patternString = ((Literal) pattern).getLabel();

		int f = 0;
		if (flagString != null) {
			for (char c : flagString.toCharArray()) {
                // See https://www.w3.org/TR/xpath-functions/#flags
				switch (c) {
				case 's':
					f |= Pattern.DOTALL;
					break;
				case 'm':
					f |= Pattern.MULTILINE;
					break;
				case 'i':
					f |= Pattern.CASE_INSENSITIVE;
					break;
				case 'x':
					f |= Pattern.COMMENTS;
					break;
				case 'd':
					f |= Pattern.UNIX_LINES;
					break;
				case 'u':
					f |= Pattern.UNICODE_CASE;
					break;
				case 'q':
					f |= Pattern.LITERAL;
					break;
				default:
					throw new IllegalArgumentException(flagString);
				}
			}
		}

		Pattern p = Pattern.compile(patternString, f);
		
		return p;
		
	}
	
}
